﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.Data.Activation;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.Module;
using StyleMVVM.Utilities;

namespace StyleMVVM.Validation.Impl
{
	public sealed class ValidationService : IValidationService, IActivationAware
	{
		private static readonly string supplemental = typeof(ValidationService).FullName;
		private IEnumerable<IValidationRuleProvider> ruleProviders;
		private IReflectionService reflectionService;

		/// <summary>
		/// Register ValidationService with DI container.
		/// </summary>
		/// <param name="container"></param>
		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(ValidationService)).As(typeof(IValidationService)).
			          ImportMethod("ImportDependencies").
			          ImportProperty("ContextProvider", false).
			          CoreExport(CoreExportStartOrder.Low).
			          AndSharedPermenantly();
		}

		public void ImportDependencies(IEnumerable<IValidationRuleProvider> ruleProviders,
		                               IReflectionService reflectionService)
		{
			this.ruleProviders = ruleProviders;
			this.reflectionService = reflectionService;
		}

		public IValidationContextProvider ContextProvider { get; set; }

		public void Activate()
		{
			if (ContextProvider == null)
			{
				ContextProvider = new DefaultValidationContextProvider();
			}
		}

		public IValidationContext GetValidationContext(object validationObject)
		{
			return GetValidationContext(validationObject, true, true);
		}

		public IValidationContext GetValidationContext(object validationObject, bool monitor)
		{
			return GetValidationContext(validationObject, monitor, true);
		}

		public IValidationContext GetValidationContext(object validationObject,
		                                               bool monitor,
		                                               bool shared)
		{
			return ContextProvider.ProvideContext(validationObject, monitor, shared, CreateNewContextAndValidate);
		}

		public IEnumerable<IValidationRule> FetchValidationRules(Type validationType, params string[] standards)
		{
			List<IValidationRule> returnValue = new List<IValidationRule>();

			foreach (IValidationRuleProvider validationRuleProvider in ruleProviders)
			{
				try
				{
					returnValue.AddRange(validationRuleProvider.ProvideRules(validationType, standards));
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while calling ProvideRules for: " + validationRuleProvider.GetType().FullName,
					             supplemental,
					             exp);
				}
			}

			return returnValue;
		}

		public IEnumerable<string> FetchPropertiesToValidate(Type validationType, params string[] standards)
		{
			List<string> returnValue = new List<string>();

			foreach (IValidationRuleProvider validationRuleProvider in ruleProviders)
			{
				try
				{
					returnValue.AddRange(validationRuleProvider.GetPropertiesToValidate(validationType, standards));
				}
				catch (Exception exp)
				{
					Logger.Error(
						"Exception thrown while calling GetPropertiesToValidate for: " + validationRuleProvider.GetType().FullName,
						supplemental,
						exp);
				}
			}

			return returnValue;
		}

		private IValidationContext CreateNewContextAndValidate(object validationObject, bool monitor, bool shared)
		{
			IInternalValidationContext validationContext = null;

			if (validationObject is IEnumerable && !(validationObject is ISkipValidatingCollection))
			{
				validationContext = new EnumerableValidationContext(validationObject,
				                                                    null,
				                                                    this,
				                                                    null,
				                                                    reflectionService,
				                                                    monitor,
				                                                    shared);
			}
			else
			{
				validationContext =
					new InstanceValidationContext(validationObject,
					                              null,
					                              this,
					                              null,
					                              reflectionService,
					                              monitor,
					                              shared);
			}

			validationContext.Validate();

			return validationContext;
		}
	}
}